-- SkyInstanceM
-- Created by chengb Jan/14/2016
-- 天空副本

module("SkyInstanceM", package.seeall);

PURE_FILE = "SkyInstanceM";

-- 配置表
local skyInstanceTable      = {};
local skyInstanceBonusTable = {};
local DRAGON_RING_ID        = 8157;
local MAX_DRAGON_RING_LEVEL = 4;

-- 内部函数声明
local checkInstanceBonusTaken;
local polluteAllGrids;
local whenBuildingBonus;
local whenPickItem;
local whenStartDungeon;
local whenLeaveDungeon;
local whenBossDie;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    skyInstanceTable = {};
    skyInstanceBonusTable = {};

    -- 载入天空副本配置表
    local sky_instance_CSV = get_sky_instance_CSV();
    for i = 1, #sky_instance_CSV do
        local row = sky_instance_CSV[i];
        local dungeonId = row.dungeon_id;
        skyInstanceTable[dungeonId] = row;
    end

    -- 载入天空副本特殊奖励配置表
    local sky_instance_bonus_CSV = get_sky_instance_bonus_CSV();
    for i = 1, #sky_instance_bonus_CSV do
        local row = sky_instance_bonus_CSV[i];
        local id = row.id;
        skyInstanceBonusTable[id] = row;
    end
end

-- 初始化
function init()
    loadCsv();

    EventMgr.removeAll("SkyInstanceM");

    -- 关注迷宫开始的事件
    EventMgr.register("SkyInstanceM", event.START_DUNGEON, function(layer)
        whenStartDungeon();
    end);

    -- 关注拾取物品的事件
    EventMgr.register("SkyInstanceM", event.PICK_UP_ITEM, function(args)
        whenPickItem(args);
    end);

    -- 关注通用建筑奖励事件的回调
    EventMgr.register("SkyInstanceM", event.BUILDING_EVENT_DONE, function(args)
        whenBuildingBonus(args);
    end);

    -- 关注玩家离开迷宫的事件
    EventMgr.register("SkyInstanceM", event.LEAVE_DUNGEON, function(args)
        whenLeaveDungeon(args);
    end);

    -- 注册BOSS死亡事件
    EventMgr.register("SkyInstanceM", event.BOSS_DIE, function(args)
        whenBossDie(args);
    end)
end

-- 检索配置
function query(dungeonId, path)
    if skyInstanceTable[dungeonId] == nil then
        return nil;
    end

    if path == nil or path == "" then
        return skyInstanceTable[dungeonId];
    end

    return skyInstanceTable[dungeonId][path];
end

-- 检索天空副本奖励配置
function queryBonus(id, path)
    if skyInstanceBonusTable[id] == nil then
        return nil;
    end

    if path == nil or path == "" then
        return skyInstanceBonusTable[id];
    end

    return skyInstanceBonusTable[id][path];
end

-- 是否是天空副本
function isSkyInstance(dungeonId)
    if "sky_instance" ~= DungeonAreaM.query(dungeonId, "type") then
        return false;
    end

    if query(dungeonId) == nil then
        return false;
    end

    return true;
end

-- 是在天空副本中
function isInSkyInstance()
    if not DungeonM.isInDungeon() then
        return false;
    end

    local dungeonId = DungeonM.getDungeonId();
    if not isSkyInstance(dungeonId) then
        return false;
    end

    return true;
end

-- 获取所有未通关的天空副本
function getUnpassedSkyInstances()
    -- 获取已探索出的天空副本
    local appearedList = ME.user.dbase:query("appeared_sky_instance", {});
    if #appearedList == 0 then
        return {};
    end

    local ret = {};
    for _, dungeonId in pairs(appearedList) do
        if not DungeonAreaM.isDungeonPassed(ME.user, dungeonId) then
            table.insert(ret, dungeonId);
        end
    end

    return ret;
end

-- 获取所有已通关的天空副本
function getPassedSkyInstances()
    -- 获取已探索出的天空副本
    local appearedList = ME.user.dbase:query("appeared_sky_instance", {});
    if #appearedList == 0 then
        return {};
    end

    local ret = {};
    for _, dungeonId in pairs(appearedList) do
        if DungeonAreaM.isDungeonPassed(ME.user, dungeonId) then
            table.insert(ret, dungeonId);
        end
    end

    return ret;
end

-- 获取已出现在雷达上的天空副本
function getExploredInstances()
    local eventIds = SkyExploreM.findExploredIdsByRule("instance");
    if #eventIds == 0 then
        return {};
    end

    local ret = {};
    for _, id in pairs(eventIds) do
        local dbase = SkyExploreM.query(id, "dbase");
        local dungeonId = dbase["dungeon_id"];
        if type(dungeonId) == "number" then
            table.insert(ret, dungeonId);
        end
    end

    return ret;
end

-- 是否是已出现在雷达上的天空副本
function isExploredInstance(dungeonId)
    local list = getExploredInstances();
    return table.indexOf(list, dungeonId) ~= -1;
end

-- 判断能否进入天空副本
function canEnterSkyInstance(dungeonId)
    if not isSkyInstance(dungeonId) then
        trace(PURE_FILE, "迷宫(%d)不是天空副本。", dungeonId);
        return false;
    end

    if isInSkyInstance() then
        trace(PURE_FILE, "玩家当前已经在天空副本中，不能重复进入。");
        return false;
    end

    if not isExploredInstance(dungeonId) then
        trace(PURE_FILE, "玩家尚未探索出天空副本(%d)，不能进入。", dungeonId);
        return false;
    end

    return true;
end

-- 进入天空副本
function startInstance(dungeonId)
    assert(type(dungeonId) == "number", "dungenId必须为number类型");

    if true ~= canEnterSkyInstance(dungeonId) then
        -- 不满足进入条件
        return false;
    end

    -- 进入迷宫
    DungeonServiceM.startEnterService(dungeonId);

    return true;
end

-- 计算探索天空副本需要消耗的雷达能量
function calcExploreCostEnergy(dungeonId)
    return FormulaM.invoke("CALC_EXPLORE_SKY_INSTANCE_ENERGY", ME.user, dungeonId);
end

-- 执行探索
function doExplore(dungeonId)
    local requireEnergy = calcExploreCostEnergy(dungeonId);

    -- 判断能量是否足够
    local curEnergy = ME.user.dbase:query("ship_energy", 0);
    if curEnergy < requireEnergy then
        trace(PURE_FILE, "雷达能量不足，需求：%d，当前：%d", requireEnergy, curEnergy);
        return false;
    end

    -- 直接向服务器发起
    Communicate.send("CMD_SKY_EXPLORE_INSTANCE", { ["dungeon_id"] = dungeonId, });
    return true;
end

-- 判断是否领取过副本的特殊奖励
function isFirstBonusTaken(id)
    local bonusRecord = ME.user.dbase:query("sky_instance_bonus", {});
    return bonusRecord[id] == 1;
end

-- 标记副本的某个特殊奖励已被领取
function setFirstBonusTaken(id)
    local bonusRecord = ME.user.dbase:query("sky_instance_bonus", {});
    bonusRecord[id] = 1;
    ME.user.dbase:set("sky_instance_bonus", bonusRecord);
end

-- 计算天空副本特殊奖励
function calcInstanceBonus(id)
    local ret;

    -- 如果已经领取过首次奖励，则使用普通奖励
    if isFirstBonusTaken(id) then
        ret = queryBonus(id, "normal_bonus");
    else
        ret = queryBonus(id, "first_bonus")
    end

    return ret;
end

-- 取得玩家获得神龙虚影奖励的次数
function getDragonShadowBonusTimes()
    return ME.user.dbase:query("dragon_shadow_times", 0);
end

-- 执行神龙虚影事件
function doDragonShadowEvent(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace(PURE_FILE, "格子(%d)不存在", pos);
        return false;
    end

    if grid.state == GRID_STATE_DIASBLE then
        trace(PURE_FILE, "格子(%d)已失效", pos);
        return false;
    end

    if grid.type ~= GRID_TYPE_DRAGON_SHADOW then
        trace(PURE_FILE, "格子(%d)不是神龙虚影", pos);
        return false;
    end

    -- 执行建筑奖励（第三个参数表示不同步，dragon_shadow 操作中会自行处理）
    if true ~= BuildingBonusM.doEvent(pos, nil, false) then
        return false;
    end

    if getDragonShadowBonusTimes() >= 1 then
        upgradeDragonRingLevel(pos);
    end

    -- 统计奖励次数
    ME.user.dbase:set("dragon_shadow_times", getDragonShadowBonusTimes() + 1);
    return true;
end

-- 是否是炎龙の吐息
function isDragonRing(classId)
    if DRAGON_RING_ID == classId then
        return true;
    end

    return false;
end

-- 获得炎龙の吐息的星级
function getDragonRingRank()
    local level = DungeonM.getDungeonMixedField("dragon_ring_level") or 0;
    return level + 1;
end

-- 升级炎龙の吐息
function upgradeDragonRingLevel(pos)
    local maxLevel = MAX_DRAGON_RING_LEVEL;

    local curLevel = DungeonM.getDungeonMixedField("dragon_ring_level") or 0;
    if curLevel >= maxLevel then
        trace("SkyInstanceM", "(%d)等级已达最高等级。", DRAGON_RING_ID);
        return false;
    end

    -- 提升等级
    DungeonM.setDungeonMixedField("dragon_ring_level", curLevel + 1);

    local msg = string.format("[SkyInstanceM]{%d}的等级提升为(%d)", DRAGON_RING_ID, curLevel + 1);
    DungeonLogM.addLog(msg);

    -- 如果穿在身上，刷新一下附加属性
    if EquipM.isEquiped(DRAGON_RING_ID) then
        PropM.refresh(ME.user);
    end

    -- 抛出玩家获得物品的事件
    EventMgr.fire(event.BONUS_ITEM, { ["classId"] = DRAGON_RING_ID, ["amount"] = 1, ["pos"] = pos,});

    return true;
end

-- 开始一层迷宫的回调
whenStartDungeon = function(args)
    if not isInSkyInstance() then
        -- 不在天空副本中
        return;
    end

    local dungeonId = DungeonM.getDungeonId();
    -- 挪到DungeonServiceM统一的地方
    -- local layer = DungeonM.currentLayer();

    -- local allOpenLayers = query(dungeonId, "all_open_layers");
    -- if type(allOpenLayers) == "table" and
    --    table.indexOf(allOpenLayers, layer) ~= -1 then
    --     -- 本层默认格子全开
    --     DungeonM.openAllGrids();
    -- end

    -- 有些副本的格子带有污染属性
    local dbase = query(dungeonId, "dbase") or {};
    if type(dbase["polluted"]) == "table" then
        polluteAllGrids(dbase["polluted"]);
    end
end

-- 是否需要全开格子
function needOpenAllGrids()
    local dungeonId = DungeonM.getDungeonId();
    local layer = DungeonM.currentLayer();

    local allOpenLayers = query(dungeonId, "all_open_layers");
    if type(allOpenLayers) == "table" and
       table.indexOf(allOpenLayers, layer) ~= -1 then
        -- 本层默认格子全开
        return true;
    end

    return false;
end

-- 检查是领取了副本特殊奖励
function checkInstanceBonusTaken(grid)
    local element = grid.element;
    local dbase = DungeonM.queryElement(element, "dbase");
    if type(dbase) ~= "table" or dbase["is_sky_instance_bonus"] ~= 1 then
        -- 不是天空副本的特殊奖励
        return;
    end

    local args = DungeonM.queryElement(element, "args");
    if type(args) ~= "table" or type(args["bonus_arg"]) ~= "table" then
        return;
    end

    local id = args["bonus_arg"]["id"];
    if type(id) ~= "number" then
        return;
    end

    if not isFirstBonusTaken(id) then
        setFirstBonusTaken(id);
    end
end

-- 拾取物品的回调
whenPickItem = function(args)
    if not isInSkyInstance() then
        -- 不在天空副本中
        return;
    end

    local pos = args.pos;
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return;
    end

    -- 检查是领取了副本特殊奖励
    checkInstanceBonusTaken(grid);
end

-- 通用建筑奖励的回调
whenBuildingBonus = function(args)
    if not isInSkyInstance() then
        -- 不在天空副本中
        return;
    end

    local pos = args.pos;
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return;
    end

    -- 检查是领取了副本特殊奖励
    checkInstanceBonusTaken(grid);
end

-- 玩家离开迷宫的回调
whenLeaveDungeon = function(args)
    -- 移除神龙虚影奖励次数
    if ME.user.dbase:query("dragon_shadow_times") then
        ME.user.dbase:delete("dragon_shadow_times");
    end
end

-- 污染所有格子
function polluteAllGrids(prop)
    local round = CombatM.getRound();
    local poisonDamage = prop[3];            -- 中毒伤害系数
    local poisonRound  = tonumber(prop[2]);  -- 中毒回合数
    local polluteRound = tonumber(prop[4]);  -- 污染回合数
    local arr = { PropM.getPropId("poisoned"), poisonDamage, poisonRound, polluteRound + round, };

    for pos = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(pos);

        -- 未开启的才会被污染
        if not grid:isOpened() and
            grid.type ~= GRID_TYPE_DOOR then
            grid.polluted = table.deepcopy(arr);

            EventMgr.fire(event.POLLUTE_LEFT_ROUND, { ["pos"] = grid:getPos(), ["left_round"] = polluteRound, } );
        end
    end
end

-- 激活当前水晶结界
function activeCrystal(pos)
    -- 先标记该石柱已经被激活
    local grid = DungeonM.getGridByPos(pos);

    if grid.type ~= GRID_TYPE_CRYSTAL_BARRIAL then
        trace("SkyInstanceM", "格子%d不匹配。", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子失效了。");
        return false;
    end

    -- 已经激活了
    if grid.actived == true then
        trace("SkyInstanceM", "水晶结界已经激活了");
        return;
    end

    grid.actived = true;

    -- 抛出激活事件。
    EventMgr.fire(event.ACTIVE_CRYSTAL_BAR, {["pos"] = pos});

    -- 与服务端一致，判断下是不是全部激活
    if SkyInstanceM.checkAllCrystalActived() then
        SkyInstanceM.openDoor();
        EventMgr.fire(event.CRYSTAL_ACTIVE_DOOR, {["pos"] = pos});
    end

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, pos);
    end

    return true;
end

-- 检查所有水晶结界是否被激活
function checkAllCrystalActived()
    -- 检查所有结界被激活
    local allGrids = DungeonM.getCurrentDungeon();

    -- 激活标示
    local allActived = true;
    for _, value in pairs(allGrids) do
        if value.type == GRID_TYPE_CRYSTAL_BARRIAL and value.actived ~= true then
            allActived = false;
            break;
        end
    end

    return allActived;
end

-- 当BOSS死亡后检查抛出奖励对话
whenBossDie = function(args)
    if not isInSkyInstance() then
        return;
    end

    -- 检查迷宫是否符合
    local dungeonId = DungeonM.getDungeonId();

    if dungeonId ~= 30011 or not DungeonM.isMaxLayer() then
        return;
    end

    -- 检查玩家是否拥有指挥官冈布奥
    local cammonderPet = 59;

    -- 找到指挥官召唤兽
    local commanderId = FormulaM.invoke("GET_COMMANDER_SUMMON_ID");

    local grid = SummonM.findSummonsByClassId(commanderId)[1];

    if grid == nil then
        return;
    end

    local pos = grid:getPos();

    local need_bonus = true;

    -- 有的话不奖励
    if PetM.getMyPet(cammonderPet) ~= nil then
        need_bonus = false;
    end

    EventMgr.fire(event.SUMMON_TRANS_BUILDING, {["pos"] = pos, ["need_bonus"] = need_bonus, ["is_commander"] = true});
end

-- 领取圣骑士的经验奖励
function takePaladinExperienceBonus(pos)
    local grid = DungeonM.getGridByPos(pos);
    local gridType = grid.type;

    assert(type(pos) == 'number');

    if not grid then
        trace("SkyInstanceM", "位置(%d)的格子不存在。", pos);
        return false;
    end

    -- 格子类型不匹配
    if grid.type ~= GRID_TYPE_PALADIN_EXPERIENCE then
        trace(PURE_FILE, "格子(%d)不是圣骑士建筑", pos);
        return false;
    end

    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "建筑格子(type = %d)已失效。", gridType);
        return false;
    end

    -- 记录下次数
    local times = grid.times or 0;

    local limitTimes = grid.limit_times;

    -- 次数是5次
    if times >= limitTimes then
        trace("SkyInstanceM", "所有奖励已经领完，次数%d", times);
        -- 提示已经掌握所有内容
        return "handling_paladin_experience", true;
    end

    times = times + 1;
    grid.times = times;

    -- 计算下奖励
    local eventBonus = FormulaM.invoke("CALC_PALADIN_EXPERIENCE_BONUS");
    local desc = "paladin_experience";

    assert(desc ~= "", "必须填写desc");

    -- 获取消耗
    local elementId = grid.element;
    local args = DungeonM.queryElement(elementId).args;
    local eventCost = args.cost;

    -- 扣除消耗
    BuildingBonusM.doBuildingCost(eventCost, desc);

    -- 执行奖励
    BuildingBonusM.doBuildingBonus(eventBonus, desc);
    grid.bonus = nil;

    -- 回合事件
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos, ["bonus"] = eventBonus["bonus"], ["cost"] = eventCost, });
    end

    -- 抛出通用建筑奖励事件
    EventMgr.fire(event.COMM_BUILDING_BONUS, {
        ["pos"]   = pos,
        ["type"]  = gridType,
        ["bonus"] = eventBonus,
        ["remainGrid"] = true,
        ["cost"] = eventCost,
    });

    return true;
end

-- 打开门
function openDoor()
    local grids = DungeonM.getCurrentDungeon();
    local door = CombatM.filterByClass(grids, GRID_TYPE_DOOR)[1];

    -- 自动开启
    door:open();

    -- 标记门已开启
    door.opened = 1;
end

-- 奖励指挥官
function doCammonderBonus(pos)
    -- 检查迷宫是否符合
    local dungeonId = DungeonM.getDungeonId();
    if dungeonId ~= 30011 or not DungeonM.isMaxLayer() then
        return false;
    end

    -- 前面有过判断
    local commanderPet = 59;

    -- 有的话不奖励
    if PetM.getMyPet(commanderPet) ~= nil then
        return false;
    end

    local grid = DungeonM.getGridByPos(pos);
    if grid.type ~= GRID_TYPE_SUMMON then
        return false;
    end

    local commanderId = FormulaM.invoke("GET_COMMANDER_SUMMON_ID");
    local summon = grid.summon;
    if not summon or summon.classId ~= commanderId then
        -- 当前格子不是指挥官召唤兽
        return false;
    end

    -- 将格子设置为无效
    grid:changeState(GRID_STATE_DISABLE);

    local bonus = {3, commanderPet, 1};
    BonusM.doBonus(bonus, "get_commander_slime");
    return true;
end

-- 锻造铁锹
function forgeSpade(pos, classId1, classId2)
    local grid = DungeonM.getGridByPos(pos);

    -- 如果不是老旧的铁砧
    if grid.type ~= GRID_TYPE_OLD_ANVIL then
        trace("SkyInstanceM", "不是老旧的铁砧");
        return false;
    end

    -- 格子失效
    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子已经 %d 已经失效。", pos);
        return false;
    end

    -- TODO: 不应该是相同的道具
    if not classId1 or not classId2 or classId1 == classId2 then
        return false;
    end

    -- 打造消耗
    local cost = grid.cost;

    if table.indexOf(cost, classId1) <= 0 or
        table.indexOf(cost, classId2) <= 0 then
        trace("SkyInstanceM", "道具不能提交。");
        return false;
    end

    -- 先扣除道具
    if not ItemM.costAmount(ME.user, classId1, 1) then
        trace("SkyInstanceM", "道具不足，不能提交。");
        return false;
    end

    if not ItemM.costAmount(ME.user, classId2, 1) then
        -- 第一个道具要补回去
        trace("SkyInstanceM", "道具不足，不能提交。");
        ItemM.addAmount(ME.user, classId1, 1);
        return false;
    end

    -- 奖励铁锹
    local bonusList = FormulaM.invoke("CALC_FORGE_SPADE_BONUS");
    for _, bonus in pairs(bonusList) do
        BonusM.doBonus(bonus, "forge_spade");
    end

    -- 打造成功
    EventMgr.fire(event.FORGE_SPADE_SUCC);

    -- 抛出通用建筑奖励事件
    EventMgr.fire(event.COMM_BUILDING_BONUS, {
        ["pos"]   = pos,
        ["type"]  = GRID_TYPE_OLD_ANVIL,
        ["bonus"] = {["bonus"] = bonusList},
        ["remainGrid"] = true,
    });
    return true;
end

-- 领取熄灭的篝火堆的奖励(当前建筑要求不消失，领取完奖励后不失效)
function takeOutBonfireBonus(pos, operateType)
    local grid = DungeonM.getGridByPos(pos);

    if not grid then
        return false;
    end

    -- 不是篝火
    if grid.type ~= GRID_TYPE_OUT_BONFIRE then
        trace("SkyInstanceM", "不是熄灭的篝火堆");
        return false;
    end

    -- 格子失效
    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子已经 %d 已经失效。", pos);
        return false;
    end

    local bonus;

    -- 如果是翻找(根据奖励次数)
    if operateType == 1 then
        local rummageTimes = grid.rummage_times or 0;
        bonus = FormulaM.invoke("GET_RUMMAGE_BONFIRE_BONUS");

        if rummageTimes < 1 then
            rummageTimes = rummageTimes + 1;
            grid.rummage_times = rummageTimes;
            BonusM.doBonus(bonus, "out_bonfire");
            doAlert(getLocStr("you_find_wood"));
        else
            doAlert(getLocStr("not_find_useful_item"));
            return false;
        end
    else
        local torch = grid.torch or 0;

        if torch < 1 then
            -- 点燃篝火堆
            torch = torch + 1;
            grid.torch = torch;
            bonus = FormulaM.invoke("GET_LIGHT_BONFIRE_BONUS");
            BonusM.doBonus(bonus, "out_bonfire");
            doAlert(getLocStr("you_get_torch"));
        else
            doAlert(getLocStr("not_find_useful_item"));
            return false;
        end
    end

    -- 回合事件
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos});
    end

    -- 抛出通用建筑奖励事件
    EventMgr.fire(event.COMM_BUILDING_BONUS, {
        ["pos"]   = pos,
        ["type"]  = GRID_TYPE_OUT_BONFIRE,
        ["bonus"] = {["bonus"] = bonus},
        ["remainGrid"] = true,
    });

    return true;
end

-- 开启保险箱
function openSkyStrongBox(pos, answer)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return false;
    end

    -- 不是保险箱
    if grid.type ~= GRID_TYPE_SKY_STRONGBOX then
        trace("SkyInstanceM", "格子不是保险箱");
        return false;
    end

    -- 格子失效
    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子已经 %d 已经失效。", pos);
        return false;
    end

    -- 回答失败时，造成9999点伤害
    if answer == 1 then
        CombatM.receiveDamage(ME.user, ME.user, 9999, 0);
    else
        local bonus = FormulaM.invoke("CALC_SKY_STRONGBOX_BONUS");
        desc = "roger_bonus";
        BuildingBonusM.doBuildingBonus(bonus, desc);

        -- 抛出通用建筑奖励事件
        EventMgr.fire(event.COMM_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = GRID_TYPE_SKY_STRONGBOX,
            ["bonus"] = bonus,
            ["remainGrid"] = false,
            ["cost"] = {},
        });
    end

    grid:changeState(GRID_STATE_DISABLE);

    -- 回合事件
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos});
    end
    return true;
end

-- 开启座钟
function openDeskClock(pos, hour, minute)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return false;
    end

    -- 不是座钟
    if grid.type ~= GRID_TYPE_DESK_CLOCK then
        trace("SkyInstanceM", "格子不是座钟");
        return false;
    end

    -- 格子失效
    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子已经 %d 已经失效。", pos);
        return false;
    end

    -- 检查下座钟时间是否对滴
    local dbaseInfo = DungeonM.queryElement(grid.element, "dbase");
    local standardHour = dbaseInfo["hour"];
    local standardMinute = dbaseInfo["minute"];

    if hour == standardHour and minute == standardMinute then
        local bonus = {["bonus"] = grid.bonus};
        desc = "desk_clock";
        BuildingBonusM.doBuildingBonus(bonus, desc);

        -- 抛出通用建筑奖励事件
        EventMgr.fire(event.COMM_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = GRID_TYPE_DESK_CLOCK,
            ["bonus"] = bonus,
            ["remainGrid"] = false,
            ["cost"] = {},
        });

        -- 答对了就关闭
        grid:changeState(GRID_STATE_DISABLE);
    else
        CombatM.receiveDamage(ME.user, ME.user, 50, 0);

        EventMgr.fire(event.ALICE_INSTANCE_DAMAGE, {
            ["pos"] = pos,
            ["type"] = GRID_TYPE_DESK_CLOCK,
        });
    end

    -- 回合事件
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos});
    end

    return true;
end

-- 判断是否调制成功
function isConcoctSucc(costList)
    local rightMaterial = FormulaM.invoke("GET_TESTBED_RIGHT_MATERIAL");

    if #costList ~= #rightMaterial then
        return false;
    end

    for k, v in pairs(rightMaterial) do
        if table.indexOf(costList, v) == -1 then
            return false;
        end
    end

    return true;
end

-- 调制物品
function testbedConcoct(pos, items)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return false;
    end

    -- 不是血腥实验台
    if grid.type ~= GRID_TYPE_BLOODY_TESTBED then
        trace("SkyInstanceM", "格子不是血腥试验台");
        return false;
    end

    -- 格子失效
    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子已经 %d 已经失效。", pos);
        return false;
    end

    -- 解析下消耗物品
    local costList = FormulaM.invoke("CALC_BLOODY_TESTBED_ITEMS", items);

    -- 扣除对应的物品
    local desc = "bloody_testbed";
    for k, v in pairs(costList) do
        local evntCost = {{1, v, 1}};
        -- 扣除消耗
        BuildingBonusM.doBuildingCost(evntCost, desc);
    end

    local isOk = isConcoctSucc(costList);

    if isOk then
        local bonus = {["bonus"] = grid.bonus};
        BuildingBonusM.doBuildingBonus(bonus, desc);

        -- 抛出通用建筑奖励事件
        EventMgr.fire(event.COMM_BUILDING_BONUS, {
            ["pos"]   = pos,
            ["type"]  = GRID_TYPE_BLOODY_TESTBED,
            ["bonus"] = bonus,
            ["remainGrid"] = true,
            ["cost"] = {},
        });

        doAlert(getLocStr("you_get_solvent"));
    else
        CombatM.receiveDamage(ME.user, ME.user, 50, 0);

        EventMgr.fire(event.ALICE_INSTANCE_DAMAGE, {
            ["pos"] = pos,
            ["type"] = GRID_TYPE_BLOODY_TESTBED,
        });
    end

    -- 回合事件
    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, { ["pos"] = pos});
    end

    return true;
end

-- 破除封印
function breakSeal(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return false;
    end

    -- 不是守卫雕像
    if grid.type ~= GRID_TYPE_GUARD_STATUE then
        trace("SkyInstanceM", "格子不是守卫雕像");
        return false;
    end

    -- 格子失效
    if grid.state == GRID_STATE_DISABLE then
        trace("SkyInstanceM", "格子已经 %d 已经失效。", pos);
        return false;
    end

    -- 扣除物品
    local desc = "guard_statue";

    -- 获取消耗
    local dbase = DungeonM.query(grid.class, "dbase");
    local eventCost  = dbase["seal_item"];

    -- 扣除消耗
    BuildingBonusM.doBuildingCost(eventCost, desc);

    -- 前置元素是否还在
    local elementId = grid.pre_element;
    local grids = DungeonM.getCurrentDungeon();

    local isOk = true;

    for k, v in pairs(grids) do
        -- 如果前置的建筑未消失
        if v.element == elementId and v.state ~= GRID_STATE_DISABLE then
            -- 提示
            doAlert(getLocStr("guard_statue_" .. v.drection));
            isOk = false;
            break;
        end
    end

    if isOk then
        -- 当前建筑消失
        grid.state = GRID_STATE_DISABLE;

        EventMgr.fire(event.ALICE_INSTANCE_DAMAGE, {
            ["pos"] = pos,
            ["type"] = GRID_TYPE_GUARD_STATUE,
            ["isOk"] = true;
        });
    end

    return true;
end

-- 解开封印的房间
function unlockSealRoom(pos)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace(PURE_FILE, "格子(%d)不存在。", pos);
        return false;
    end

    -- 已经解锁
    if DungeonM.getDungeonMixedField("unlock_sealroom") == 1 then
        trace(PURE_FILE, "当前已经解开封印的房间。");
        return false;
    end

    -- 获取消耗
    local dbase = DungeonM.query(grid.class, "dbase");
    local eventCost  = dbase["seal_item"];

    if ItemM.getAmount(ME.user, eventCost[2]) < eventCost[3] then
        CombatM.receiveDamage(ME.user, ME.user, 50, 0);

        EventMgr.fire(event.ALICE_INSTANCE_DAMAGE, {
            ["pos"] = pos,
            ["type"] = GRID_TYPE_SEAL_ROOM,
        });
    else
        -- 标记解锁
        DungeonM.setDungeonMixedField("unlock_sealroom", 1);

        -- 扣除消耗
        BuildingBonusM.doBuildingCost(eventCost, desc);

        -- 转换成对应的元素
        local toElement = dbase.trans_element;
        local toClass = DungeonM.queryElement(toElement, "class");
        local para = { ["class"] = toClass, ["element"] = toElement };
        DungeonM.transformGrid(toClass, pos, GRID_STATE_OPEN, para);

        -- 抛出事件
        EventMgr.fire(event.UNLOCK_SHOWCASE, { ["pos"] = pos, });
    end

    return true;
end

-- 检查所有守卫雕像都清除
function guardStatueAllClear()
    local grids = DungeonM.getCurrentDungeon() or {};

    local isClear = true;

    for k, grid in pairs(grids) do
        -- 如果还有守卫雕像未消失
        if grid.type == GRID_TYPE_GUARD_STATUE and grid.state ~= GRID_STATE_DISABLE then

            isClear = false;
            break;
        end
    end

    return isClear;
end

-- 天空副本首次奖励是否被领取
function isFirstRewardsTaken(grid)
    local element = grid.element;
    local dbase = DungeonM.queryElement(element, "dbase");
    if type(dbase) ~= "table" or dbase["is_sky_instance_bonus"] ~= 1 then
        -- 不是天空副本的特殊奖励
        return true;
    end

    local args = DungeonM.queryElement(element, "args");
    if type(args) ~= "table" or type(args["bonus_arg"]) ~= "table" then
        return true;
    end

    local id = args["bonus_arg"]["id"];
    if type(id) ~= "number" then
        return true;
    end

    if not isFirstBonusTaken(id) then
        return false;
    end

    return true;
end
